// System includes
#include <QDebug>
#include <QDateTime>
#include <QtConcurrentRun>
#include <string>

// Local includes
#include "sc.h"
#include "Server.h"
#include "sclang.h"
#include "oscpack/osc/OscOutboundPacketStream.h"
//#include "sc++/synth.h"
#include "sc++/bus.h"
#include "SC_Types.h"
#include "SC_SndBuf.h"


SCLang::SCLang(YigClient *yigClient, QString applicationDir, int clientPort, int mServerPort, QObject *parent) :
    scSynthClient(yigClient),
    QObject(parent),
    mServerPort(mServerPort),
    audioPipePort(32254),
    audioPipeIP("127.0.0.1"),
    mAudioPiping(false)
{
    setClientPort(clientPort);
    //ServerOptions options;
    //options.serverPort = mServerPort;
    //options.pluginPath = QString(qApp->applicationDirPath() + "/sc++/plugins");
    //options.synthDefPath = QString(qApp->applicationDirPath() + "/sc++/synthdefs");
    //server = new Server(options);
    sc::ServerOptions options;
    options.setPortNum(mServerPort);
    //std::string pluginPath = applicationDir.toStdString() + "/plugins";
    //std::string synthDefPath = applicationDir.toStdString() + "/synthdefs";
    //options.pluginPath = pluginPath.c_str();
    //options.synthDefPath = synthDefPath.c_str();
    options.pluginPath = "./Yig.app/Contents/MacOS/plugins";
    options.synthDefPath = "./Yig.app/Contents/MacOS/synthdefs";
    server = new sc::Server(options);
    //server = new sc::Server();
    serverBootStatus = true; // WE NEED TO FIGURE THIS OUT FOR THE FUTURE
    nodeIDCounter = 1001;
    statusTimer.setInterval(1000);
    QObject::connect(&statusTimer, SIGNAL(timeout()), this, SLOT(sendStatusRequest()), Qt::QueuedConnection);
    //QObject::connect(this, SIGNAL(booting()), server, SLOT(boot()));
    //QObject::connect(server, SIGNAL(finishedBooting()), this, SLOT(handleSCSynthBoot()));
    //QObject::connect(server, SIGNAL(finishedQuitting()), this, SLOT(handleSCSynthQuit()));

    // Audio Piping
    //QObject::connect(server->getServerThread(), SIGNAL(pipeAudio(AudioBuffer*)), this, SLOT(pipeAudio(AudioBuffer*)));

    locker.lockForWrite();
    audioPipeBuffer = new SndBuf;
    audioPipeBuffer->samplerate = server->getOptions()->mPreferredSampleRate;
    audioPipeBuffer->sampledur = 1 / server->getOptions()->mPreferredSampleRate;
    audioPipeBuffer->channels = 1;
    audioPipeBuffer->samples = audioPipeBuffer->channels * server->getOptions()->mPreferredHardwareBufferFrameSize;
    audioPipeBuffer->frames = server->getOptions()->mBufLength;
    audioPipeBuffer->data = (float*) malloc(sizeof(float) * server->getOptions()->mPreferredHardwareBufferFrameSize);
    memset(audioPipeBuffer->data, 0, sizeof(float) * server->getOptions()->mPreferredHardwareBufferFrameSize);
    locker.unlock();
}

SCLang::~SCLang()
{
    /*
    if(silence)
    {
        silence->free();
        delete silence;
    }*/

    locker.lockForWrite();
    delete[] (float*) audioPipeBuffer->data;
    audioPipeBuffer->data = 0;
    delete audioPipeBuffer;
    locker.unlock();

    if(recording)
        endRecording();
    delete server;
}

void SCLang::setClientPort(int clientPort)
{
    mClientPort = clientPort;
}

int SCLang::getClientPort()
{
    return mClientPort;
}

void SCLang::bootServer()
{
    server->boot();
    emit booting();
    //handleSCSynthBoot();
    serverSetupOnBoot();
    qDebug() << "SCSynth done booting.";
}

bool SCLang::getServerBootStatus()
{
    return serverBootStatus;
}

sc::Server* SCLang::getServer()
{
    return server;
}

void SCLang::sendServer(const char* command)
{
    char buffer[PACKET_BUFFER_SIZE];
    osc::OutboundPacketStream packet( buffer, PACKET_BUFFER_SIZE );
    packet << osc::BeginMessage(command) << osc::EndMessage;
    scSynthClient->send(packet, mServerPort);
}

void SCLang::sendServer(const char *command, int nodeID)
{
    char buffer[PACKET_BUFFER_SIZE];
    osc::OutboundPacketStream packet( buffer, PACKET_BUFFER_SIZE );
    packet << osc::BeginMessage(command) << nodeID << osc::EndMessage;
    scSynthClient->send(packet, mServerPort);
}

void SCLang::sendServer(const char *command, int nodeID, const char *argument, int value)
{
    char buffer[PACKET_BUFFER_SIZE];
    osc::OutboundPacketStream packet( buffer, PACKET_BUFFER_SIZE );
    packet << osc::BeginMessage(command) << nodeID << argument << value << osc::EndMessage;
    scSynthClient->send(packet, mServerPort);
}

void SCLang::sendServer(const char *command, int nodeID, const char *argument, float value)
{
    char buffer[PACKET_BUFFER_SIZE];
    osc::OutboundPacketStream packet( buffer, PACKET_BUFFER_SIZE );
    packet << osc::BeginMessage(command) << nodeID << argument << value << osc::EndMessage;
    scSynthClient->send(packet, mServerPort);
}

void SCLang::sendServer(const char *command, int nodeID, QList<const char *> arguments, QList<int> values)
{
    char buffer[PACKET_BUFFER_SIZE];
    osc::OutboundPacketStream packet( buffer, PACKET_BUFFER_SIZE );
    packet << osc::BeginMessage(command) << nodeID;

    for(int i = 0; i < arguments.size(); i++)
    {
        packet << arguments.at(i) << values.at(i);
    }

    packet << osc::EndMessage;
    scSynthClient->send(packet, mServerPort);
}

void SCLang::sendServer(const char *command, int nodeID, QList<const char *> arguments, QList<float> values)
{
    char buffer[PACKET_BUFFER_SIZE];
    osc::OutboundPacketStream packet( buffer, PACKET_BUFFER_SIZE );
    packet << osc::BeginMessage(command) << nodeID;

    for(int i = 0; i < arguments.size(); i++)
    {
        packet << arguments.at(i) << values.at(i);
    }

    packet << osc::EndMessage;
    scSynthClient->send(packet, mServerPort);
}

void SCLang::handleQuit()
{
    if(mAudioPiping)
    {
        mAudioPiping = false;
        pipeFuture.waitForFinished();
    }

    server->quit();
    qDebug() << "SCLang is shutting down";
    /*
    while(server->getBootStatus())
    {
        qDebug() << serverBootStatus;
    }*/
    scSynthClient->stop();
    emit quitting();
}

void SCLang::sendStatusRequest()
{
    char buffer[PACKET_BUFFER_SIZE];
    osc::OutboundPacketStream packet( buffer, PACKET_BUFFER_SIZE );
    packet << osc::BeginMessage( "/status" ) << osc::EndMessage;
    scSynthClient->send(packet, mServerPort);
}

void SCLang::record(bool recording)
{
    if(recording)
        beginRecording();
    else
        endRecording();
}

void SCLang::pipeAudio()
{
    while(mAudioPiping)
    {
        if(audioPipeBuffer)
        {

            try
            {
                if(server->getScopeBuf(0, audioPipeBuffer));
                {
                    QReadLocker lock(&locker);
                    char buffer[PACKET_BUFFER_SIZE * 2];
                    osc::OutboundPacketStream packet( buffer, PACKET_BUFFER_SIZE * 2 );
                    packet << osc::BeginMessage( "/audio" );

                    for(int i = 0; i < server->getOptions()->blockSize; ++i)
                    {
                        if(packet.Size() < (PACKET_BUFFER_SIZE * 2))
                        {
                            packet << ((float*)audioPipeBuffer->data)[i];
                        }
                    }

                    packet << osc::EndMessage;
                    scSynthClient->send(audioPipeIP, packet, audioPipePort);
                }

            }

            catch(...)
            {
                qDebug() << "ERROR: AUDIO PIPE READ FAILED";
            }
        }
        // 33333 = 1/30 ie 30 fps
        usleep(33333);
    }
}

void SCLang::toggleAudioPipe(bool audioPiping, QString ip, int port)
{
    mAudioPiping = audioPiping;
    audioPipePort = port;
    audioPipeIP = ip;
    pipeFuture = QtConcurrent::run(this, &SCLang::pipeAudio);
}

void SCLang::requestAudioBuffers(QHash<int,SndBuf*>* buffers)
{
    //qDebug() << "AUDIO BUFFERS REQUESTED";
    QHashIterator<int, SndBuf*> iter(*buffers);
    while(iter.hasNext())
    {
        iter.next();
        //qDebug() << "AUDIO BUFFER INDEX: " << iter.key();
        server->getScopeBuf(iter.key(), iter.value());
        //qDebug() << "SCOPE BUFFER INDEX 0: " << iter.value()->data[0];
    }
}

void SCLang::beginRecording()
{
    if(recording)
        endRecording();

    recording = true;
    server->record();

    /*
    nodeIDCounter++;

    char buffer[PACKET_BUFFER_SIZE];
    osc::OutboundPacketStream packet( buffer, PACKET_BUFFER_SIZE );
    packet << osc::BeginMessage( "/b_alloc" ) << 0 << 65536 << 2 << osc::EndMessage;
    scSynthClient->send(packet, mServerPort);

    char buffer2[PACKET_BUFFER_SIZE];
    osc::OutboundPacketStream packet2( buffer2, PACKET_BUFFER_SIZE );
    const char* path = qPrintable(QString(qApp->applicationDirPath() + "/yigRecording.aiff"));
    packet2 << osc::BeginMessage( "/b_write" ) << 0 << path << "aiff" << "int24" << 0 << 0 << 1 << osc::EndMessage;
    scSynthClient->send(packet2, mServerPort);

    char buffer3[PACKET_BUFFER_SIZE];
    osc::OutboundPacketStream packet3( buffer3, PACKET_BUFFER_SIZE );
    packet3 << osc::BeginMessage( "/s_new" ) << "YigRecord" << nodeIDCounter << TailOfGroup << 0 << osc::EndMessage;
    scSynthClient->send(packet3, mServerPort);

    recordingSynth = new Synth(this, nodeIDCounter, QString("YigRecord"));*/
}

void SCLang::endRecording()
{
    recording = false;
    server->stopRecording();
    //sendServer("/b_close", 0);
    //recordingSynth->free();
    //delete recordingSynth;
}

void SCLang::handleSCSynthBoot()
{
    serverSetupOnBoot();
}

void SCLang::handleSCSynthQuit()
{
    serverBootStatus = false;
}

void SCLang::serverSetupOnBoot()
{
    //const char* path = qPrintable(server->options.synthDefPath);
    char buffer[PACKET_BUFFER_SIZE];
    osc::OutboundPacketStream packet( buffer, PACKET_BUFFER_SIZE );
    //packet << osc::BeginMessage("/d_loadDir") << path << osc::EndMessage;
    QString path = qApp->applicationDirPath() + "/synthdefs";
    server->loadDirectory(qPrintable(path));
    packet << osc::BeginMessage( "/status" ) << osc::EndMessage;
    scSynthClient->send(packet, mServerPort);

    //packet.Clear();
    // Creates the default_group node on the server with an ID of 1 at the head of the root node, which is ID 0
    //packet << osc::BeginMessage( "/g_new" ) << 1 << HeadOfGroup << 0 << osc::EndMessage;
    //scSynthClient->send(packet, mServerPort);
    //defaultGroup = new Group(this, 0);
    serverBootStatus = true;
    statusTimer.start();
    //silence = newSynth("YigSilence");
    emit finishedBooting();
}
